﻿/// <reference path="_namespace.js" />
/// <reference path="Moveable.js" />

/*****************************************************************************
Ajax library class definition. Kevin Robinson 2008.
Class: ajax.xna.MoveController
/*****************************************************************************/

ajax.lang.addNamespace("ajax.xna");
//-----------------------------------------------------------------------------
ajax.xna.MoveController = function(doc)
{
    ajax.xna.MoveController.superConstructor.call(this, doc);
    
    this.getEvent(window, "onBeforeMeshRender");
    this.getEvent(window, "onAfterMeshRender");
    
    this._initialize();
}
ajax.lang.extend("ajax.xna.MoveController", "ajax.xna.Moveable");
if ($getDesignMode())
    ajax.xna.MoveController.prototype = ajax.xna.Moveable.prototype;
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Statics

ajax.xna.MoveController.createWithChild = function(childMoveController)
{///<returns type="ajax.xna.MoveController"/>
    var result = new ajax.xna.MoveController();
    result.addMoveController(childMoveController, true);
    return result;
}

ajax.xna.MoveController.createWithMesh = function(mesh)
{///<returns type="ajax.xna.MoveController"/>
    var result = new ajax.xna.MoveController();
    result.getMeshes().append(mesh);
    return result;
}

ajax.xna.MoveController.cast = function(obj)
{///<returns type="ajax.xna.MoveController"/>
    return obj;
}


//-----------------------------------------------------------------------------
// Public properties
ajax.xna.MoveController.prototype._name             = "";
ajax.xna.MoveController.prototype._parent           = null;
ajax.xna.MoveController.prototype._nextParent       = null;
ajax.xna.MoveController.prototype._moveControllers  = Array.prototype;
ajax.xna.MoveController.prototype._meshes           = Array.prototype;
ajax.xna.MoveController.prototype._colliders        = Array.prototype;

ajax.xna.MoveController.prototype._initialize = function()
{
    ajax.xna.MoveController.superClass._initialize.call(this);
    this._moveControllers = [];
    this._meshes          = [];
    this._colliders       = [];
}

//-----------------------------------------------------------------------------
// Public Accessors
ajax.xna.MoveController.prototype.getName       = function()
{///<returns type="string"/>
    return this._name;
}
ajax.xna.MoveController.prototype.setName       = function(value) { this._name = value; } 
ajax.xna.MoveController.prototype.getMeshes     = function()
{///<returns type="Array" elementType="ajax.geometry.Mesh"/>
    return this._meshes;
}
ajax.xna.MoveController.prototype.getColliders  = function()
{///<returns type="Array" elementType="ajax.geometry.Mesh"/>
    return this._colliders;
}
ajax.xna.MoveController.prototype.getNextParent = function()
{///<returns type="ajax.xna.MoveController"/>
    return this._nextParent;
}
ajax.xna.MoveController.prototype.setNextParent = function(value) { this._nextParent = value; }
ajax.xna.MoveController.prototype.getParent     = function()
{///<returns type="ajax.xna.MoveController"/>
    return this._parent;
}
ajax.xna.MoveController.prototype.setNullParent = function() { this._parent = null; }




//-----------------------------------------------------------------------------
// Public methods
ajax.xna.MoveController.prototype.updateMatrices = function()
{///<summary>Call this during Update to apply any offsets made to the local matrix to the world matrix</summary>
    // Update THIS World matrix - based on our parent
    if (this.getParent() != null)    
        this.setWorldMatrix( ajax.math.Matrix.multiply(this.getLocalMatrix(), this.getParent().getWorldMatrix()) );
    else    
        this.setWorldMatrix(this.getLocalMatrix());
    
    // Loop through our children and update THEIR matrices
    for (var i = 0; i < this._moveControllers.length; i++)
        this._moveControllers[i].updateMatrices();    
}

ajax.xna.MoveController.prototype.addMoveController = function(value, bTellChild)
{///<summary>Adds a MoveController to our list of child moveControllers. Note: Any child can only have ONE parent at any one time.</summary>
    //This child can only have ONE parent, US!
    // So that we are it's daddy now - so it can tell it's old daddy bye bye
    if (bTellChild)
        value.setParent(this, false);    
    // and add them to OUR list
    this._moveControllers.append(value, true);
}

ajax.xna.MoveController.prototype.removeMoveController = function(value)
{///<summary>Removes the passed move controller from our list</summary>
    this._moveControllers.remove(value);
}

ajax.xna.MoveController.prototype.setParent     = function(value, bTellParent)
{       
    // We can only set a parent that does NOT exist further down our node...
    // so take this new node and track back to root to see if WE appear.
    // If we do, then bail...
    var alreadyRelated = value.getParent();
    while (alreadyRelated != null)
    {
        if (alreadyRelated == this)
            return;
        alreadyRelated = alreadyRelated.getParent();
    }
    
    // Tell our old daddy bye bye
    if (this._parent != null)
        this._parent.removeMoveController(this);
        
    // We are now an orphan!! So we set our offset & world to be absolute to the world!(????)
    
    // Should we tell our new daddy to love us?
    if (bTellParent)
        value.addMoveController(this, false);
        
    // Right, we must alter our offset so that we stay in the same world position, but now as a result of offsetting from 
    // our new parent.
    // We do that by setting our local matrix to be:
    // this.Local = this.World * Inverse(newParent.World);
    // We also need to update our oldLocalPosition - so get a vector from new to old.#
    
    var vecNew2Old = ajax.math.Vector3.subtract(this.getLocalMatrix().getPositionVector(), this.getOldLocalPositionVector());
    // Redo our offset (????)
    
    // We have to make it so that when updateMatrices is called - our new parent's World * our Local offset = our Original World
    this.setLocalMatrix(ajax.math.Matrix.multiply(this.getWorldMatrix(), ajax.math.Matrix.invert(value.getWorldMatrix()) ));        
    // reset our old offset to be current posotion + new2Old
    this.setOldLocalPositionVector(ajax.math.Vector3.add(this.getLocalMatrix().getPositionVector, vecNew2Old));
    
    // Add the parent (if not added because we told the parent about all this).
    this._parent = value;
    
    // Call our UpdateMatrices so any children of us can also get the update
    this.updateMatrices();        
}

ajax.xna.MoveController.prototype.processNextParent = function()
{
    if (this._nextParent != null)
    {
        // this should work as we pass the object handle by VALUE - so setting to null right after should not affect
        // the method we passed it to
        this.setParent(this._nextParent, true);
        this._nextParent = null;
        return true;
    }
    return false;
}

ajax.xna.MoveController.prototype.renderMeshes = function(device)
{///<summary>Renders all our meshes (if we have any) and prompts our child MoveControllers to render theirs too.</summary>
    
    this.onBeforeMeshRender.call(this);
    
    // Render all our meshes if we have any    
    if (this._meshes.length > 0)
    {
        device = ajax.drawing.Device3D.cast(device);
        device.transform.world = this.getWorldMatrix();
        for (var i = 0; i < this._meshes.length; i++)
        {
            var mesh = ajax.geometry.Mesh.cast(this._meshes[i]);
            mesh.render(device);
        }
    }
    
    this.onAfterMeshRender.call(this);
    
    // Call RenderMeshes on our child MoveControllers
    for (var i = 0; i < this._moveControllers.length; i++)
    {
        // Call its renderMeshes method.
        this._moveControllers[i].renderMeshes(device);
    }
    
}

ajax.xna.MoveController.prototype.computeCallbackCollisionsStart = function(objectToAffect)
{///<summary>Tests the objectToAffect's movement against all collision meshes attached to this node or any child nodes. Each hit will raise the mesh's onNearCallback (in e-space) and onCollisionCallback (world space) events that clients can listen for and respond to (providing collision response). Returns true if any collisions took place. Use this (overload) when starting a collision test from root, so it will cacsade down automatically, so you don't have to work out the invParentTransform yourself.</summary>
 ///<param name="objectToAffect" type="ajax.xna.MoveController">The MoveController Node to affect</param> 
    var parentTransform = null; 
    if (objectToAffect.getParent())
    {
        parentTransform = ajax.math.Matrix.cast(objectToAffect.getParent().getWorldMatrix());
    }     
    
    var invParentTransform = (parentTransform) ? parentTransform.inverse() : null;
    return this.computeCallbackCollisions(objectToAffect, invParentTransform);
}

ajax.xna.MoveController.prototype.computeCallbackCollisions = function(objectToAffect, invParentTransform)
{///<summary>Tests the objectToAffect's movement against all collision meshes attached to this node or any child nodes. Each hit will raise the mesh's onNearCallback (in e-space) and onCollisionCallback (world space) events that clients can listen for and respond to (providing collision response).</summary>
 ///<param name="objectToAffect" type="ajax.xna.MoveController">The MoveController Node to affect</param>
 ///<param name="invParentTransform" type="ajax.math.Matrix" mayBeNull="true">The Pre-calculated INVERSE of the objectToAffect's Parent WorldMatrix. Can be null if the objectToAffect does not have a parent.</param> 
    
    // if we get any collisions, return true
    var bRet = false;
    
    // Compute collisions for OUR mesh colliders (if we have any).
    if (this.getColliders().length > 0)
    {
        // Call the Collision method on the mesh, passing US as the transformer
        for (var i = 0; i < this.getColliders().length; i++)
        {
            var mesh = ajax.geometry.Mesh.cast(this.getColliders()[i]);            
            bRet = mesh.computeCallbackCollisions(objectToAffect, invParentTransform, this);
        }
    }
    
    // Now call our child controllers, passing the object to affect
    for (var i = 0; i < this._moveControllers.length; i++)
    {
        // Make a note of the size
        var size = this._moveControllers.length;
        var controller = ajax.xna.MoveController.cast(this._moveControllers[i]);
        if (controller.computeCallbackCollisions(objectToAffect, invParentTransform))
            bRet = true;
        
        // If the size is reduced, it means that some move controllers may have been
        // removed by a client of the callback.
        // So we must reduce i by the same amount to save skipping any controllers
        var offset = size - this._moveControllers.length;
        i-=offset;        
    }
    return bRet;
}


